package com.crawler.waf.filter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.crawler.waf.exceptions.extendExceptions.WafSimpleException;
import com.crawler.waf.support.Constants;
/**
 * 
 * ie8/ie9跨域支持过滤器 
 *
 */
public class WafHttpMethodOverrideFilter extends OncePerRequestFilter {
	
	private static final String defaultCharset = "UTF-8";
	
	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		logger.info("WafHttpMethodOverrideFilter doFilterInternal start");
		String proxyValue = request.getParameter(Constants.PROXY_PARAM);
		if ("POST".equals(request.getMethod()) && "body".equals(proxyValue)) {
			/**
			 * 获取body内容
			 */
			String requestBody = IOUtils.toString(request.getInputStream(), defaultCharset);
			logger.info("Override Method Filter bodyString" + requestBody);
			JSONObject bodyJson = null;
			ByteArrayOutputStream os =new ByteArrayOutputStream(); 
			HttpServletResponseWrapper httpResponseWrapper = new HttpServletResponseWrapper(response, os);
			try {
				bodyJson = JSON.parseObject(requestBody);
				if (bodyJson != null) {
					// 解析大body包含的参数
					String methodValue = bodyJson.getString(Constants.METHOD_PARAM);
					JSONObject headersObject = bodyJson
							.getJSONObject(Constants.HEADERS_PARAM);
					String bodyObject = bodyJson.getString(Constants.BODY_PARAM);
					String method = methodValue.toUpperCase(Locale.ENGLISH);
					HttpServletRequest httpRequestWrapper = new HttpMethodRequestWrapper(
							request, method, headersObject, bodyObject);

					filterChain.doFilter(httpRequestWrapper, httpResponseWrapper);
					 //刷新response的输出流, 在某些情况下, 如果不刷新的话, 会导致部分信息还在缓存中, 而没有写入os中  
					handleResponse(os, httpResponseWrapper, response, headersObject);
				} else {
					filterChain.doFilter(request, response);
				}
			} catch (Exception e) {
				logger.error(e.getMessage(), e);
				//抛出异常
			    request.setAttribute("exception", new WafSimpleException(HttpStatus.PAYMENT_REQUIRED, "WAF/PROXY_PARAM_ERROR", "Parameter format is not correct"));
			    request.getRequestDispatcher("/exception").forward(request, httpResponseWrapper);
			    handleResponse(os, httpResponseWrapper, response, null);
			}
		} else {
			filterChain.doFilter(request, response);
		}
	}
	
	/**
	 * 
	* @Title: handleResponse 
	* @Description: 处理响应
	* @param @param os
	* @param @param httpResponseWrapper
	* @param @param response
	* @param @param headersObject
	* @param @throws IOException    设定文件 
	* @return void    返回类型 
	* @throws
	 */
	private void handleResponse(ByteArrayOutputStream os, HttpServletResponseWrapper httpResponseWrapper, HttpServletResponse response, JSONObject headersObject) throws IOException {
		JSONObject resJSONObject = new JSONObject();
		resJSONObject.put(Constants.HEADERS_PARAM, headersObject);
	    resJSONObject.put(Constants.STATUS, httpResponseWrapper.getStatus());
	    resJSONObject.put(Constants.STATUS_TEXT, "OK");   
	    
        String body = new String(os.toByteArray(), defaultCharset);
        logger.info("body.length():"+body.length()+",responseBody:"+body);
        if (!StringUtils.isEmpty(body)) {
        	resJSONObject.put(Constants.BODY_PARAM, body);
		}
        
        
        logger.info(httpResponseWrapper.getOutputStream().toString());
        
        String responseContent = resJSONObject.toString();
        response.setCharacterEncoding(defaultCharset);
        logger.info("resContent"+responseContent+",response.getContentType():"+response.getContentType());
        response.setContentLength(-1);
        PrintWriter out = response.getWriter();
        out.print(resJSONObject.toString()); 
        out.flush(); 
        out.close(); 
	}

	/**
	 * Simple {@link HttpServletRequest} wrapper that returns the supplied
	 * method for {@link HttpServletRequest#getMethod()}.
	 */
	private static class HttpMethodRequestWrapper extends
			HttpServletRequestWrapper {

		private final String method;
		private final JSONObject headers;
		private final String body;

		public HttpMethodRequestWrapper(HttpServletRequest request,
				String method, JSONObject headers, String body) {
			super(request);
			this.method = method;
			this.headers = headers;
			this.body = body;
		}

		@Override
		public long getDateHeader(String name) {
			return headers.getLongValue(name);
		}

		@Override
		public String getContentType() {
			return headers.getString("Content-Type");
		}

		@Override
		public String getHeader(String name) {
			return headers.getString(name);
		}

		/**
		 * 
		 * @Title: getHeaders
		 * @Description: 获取指定请求头的多个值
		 * @param @param name
		 * @param @return 设定文件
		 * @return Enumeration<String> 返回类型
		 * @throws
		 */
		@Override
		public Enumeration<String> getHeaders(String name) {
			Iterator<Map.Entry<String, Object>> it = this.headers.entrySet()
					.iterator();
			List<String> values = new LinkedList<String>();
			while (it.hasNext()) {
				Map.Entry<String, Object> entry = it.next();
				if (entry.getKey().equals(name)) {
					values.add(entry.getValue().toString());
				}
			}
			return Collections.enumeration(values);
		}

		/**
		 * 
		 * @Title: getHeaderNames
		 * @Description: 获取所有请求头的名称
		 * @param @return 设定文件
		 * @return Enumeration<String> 返回类型
		 * @throws
		 */
		@Override
		public Enumeration<String> getHeaderNames() {
			return Collections.enumeration(this.headers.keySet());
		}

		@Override
		public String getAuthType() {
			return super.getAuthType();
		}

		@Override
		public String getCharacterEncoding() {
			return super.getCharacterEncoding();
		}

		@Override
		public int getContentLength() {
			return super.getContentLength();
		}

		@Override
		public int getIntHeader(String name) {
			return headers.getIntValue(name);
		}

		@Override
		public String getMethod() {
			return this.method;
		}
		
		@Override
		public ServletInputStream getInputStream() throws IOException {
			final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(defaultCharset));
			return new ServletInputStream() {
				public int read() throws IOException {
					return byteArrayInputStream.read();
				}

				@Override
				public boolean isFinished() {
					return false;
				}

				@Override
				public boolean isReady() {
					return false;
				}

				@Override
				public void setReadListener(ReadListener readListener) {
				}
			};
		}

		@Override
		public BufferedReader getReader() throws IOException {
			return new BufferedReader(new InputStreamReader(
					this.getInputStream()));
		}
	}

	private static class HttpServletResponseWrapper extends
			javax.servlet.http.HttpServletResponseWrapper implements
			Serializable {

		private static final long serialVersionUID = -6823255025479924073L;

		public HttpServletResponseWrapper(HttpServletResponse response,
				OutputStream outputStream) {
			super(response);
			statusCode = 200;
			this.servletOutputStream = new ServletOutputStreamWrapper(outputStream);
		}

		public ServletOutputStream getOutputStream() {
			return servletOutputStream;
		}

		public void setStatus(int code) {
			statusCode = code;
			super.setStatus(200);
		}

		public void sendError(int i, String string) throws IOException {
			statusCode = i;
			super.sendError(i, string);
		}

		public void sendError(int i) throws IOException {
			statusCode = i;
			super.sendError(i);
		}

		public void sendRedirect(String string) throws IOException {
			statusCode = 302;
			super.sendRedirect(string);
		}

		public void setStatus(int code, String msg) {
			statusCode = code;
			super.setStatus(code);
		}

		public int getStatus() {
			return statusCode;
		}

		public void setContentLength(int length) {
			contentLength = length;
			super.setContentLength(length);
		}

		public int getContentLength() {
			return contentLength;
		}

		public void setContentType(String type) {
			contentType = type;
			super.setContentType(type);
		}

		public String getContentType() {
			return contentType;
		}

		public PrintWriter getWriter() throws IOException {
			if (writer == null)
				writer = new PrintWriter(new OutputStreamWriter(
						servletOutputStream, getCharacterEncoding()), true);
			return writer;
		}

		public void addHeader(String name, String value) {
			String header[] = { name, value };
			headers.add(header);
			super.addHeader(name, value);
		}

		public void setHeader(String name, String value) {
			addHeader(name, value);
		}

		public Collection<String[]> getHeaders() {
			return headers;
		}

		public void addCookie(Cookie cookie) {
			cookies.add(cookie);
			super.addCookie(cookie);
		}

		public Collection<Cookie> getCookies() {
			return cookies;
		}

		public void flushBuffer() throws IOException {
			flush();
			super.flushBuffer();
		}

		public void reset() {
			super.reset();
			cookies.clear();
			headers.clear();
			statusCode = 200;
			contentType = null;
			contentLength = 0;
		}

		public void resetBuffer() {
			super.resetBuffer();
		}

		public void flush() throws IOException {
			if (writer != null)
				writer.flush();
			servletOutputStream.flush();
		}

		public String encodeRedirectUrl(String s) {
			return super.encodeRedirectURL(s);
		}

		public String encodeUrl(String s) {
			return super.encodeURL(s);
		}

		private int statusCode;
		private int contentLength;
		private String contentType;
		private final List<String[]> headers = new ArrayList<String[]>();
		private final List<Cookie> cookies = new ArrayList<Cookie>();
		private ServletOutputStream servletOutputStream;
		private PrintWriter writer;
	}
	
	private static class ServletOutputStreamWrapper extends ServletOutputStream {
		
		public ServletOutputStreamWrapper(OutputStream stream) {
			this.stream = stream;
		}

		public void write(int b) throws IOException {
			stream.write(b);
		}

		public void write(byte b[]) throws IOException {
			stream.write(b);
		}

		public void write(byte b[], int off, int len) throws IOException {
			stream.write(b, off, len);
		}

		private OutputStream stream;

		@Override
		public boolean isReady() {
			return false;
		}

		@Override
		public void setWriteListener(WriteListener writeListener) {
		}
	}


}
